/*****************************************************************************
 * hal_stm32f4_gpio.c
 *
 * Copyright (C) 2019 Jeasonvor <1101627719@qq.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ****************************************************************************/

/*****************************************************************************
 * Included Files
 ****************************************************************************/
#include "plum_api.h"
#include "hal/source/hal_stm32f4/hal_stm32f4.h"
/*****************************************************************************
 * Trace Definitions
 ****************************************************************************/

#define HAL_STM32_GPIO_GET_PORT(id) (plum_u8)(((plum_u16)id) >> 8)

#define HAL_STM32_GPIO_GET_PIN(id) (plum_u8)(id)

#define HAL_STM32_GPIO_IRQLINE_INVAILD (0xFFFF)

/*****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/*****************************************************************************
 * Private Types
 ****************************************************************************/
struct gpio_irq {
    plum_u32 pin;
    plum_void (*handler)(plum_void);
    struct gpio_irq *next;
};

/*****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/*****************************************************************************
 * Private Data
 ****************************************************************************/

/* stm32 gpio irq table */
PLUM_PRIVATE struct gpio_irq irq_hd;

/*****************************************************************************
 * Public Data
 ****************************************************************************/

/*****************************************************************************
 * Private Functions
 ****************************************************************************/

PLUM_PRIVATE GPIO_TypeDef *hal_stm32_gpio_port(plum_u32 id)
{
    GPIO_TypeDef *GPIOx = NULL;

    switch (HAL_STM32_GPIO_GET_PORT(id)) {
#if defined(GPIOA)
        case PLUM_HAL_ID_GPIO_A_BASE:
            GPIOx = GPIOA;
            break;
#endif

#if defined(GPIOB)
        case PLUM_HAL_ID_GPIO_B_BASE:
            GPIOx = GPIOB;
            break;
#endif

#if defined(GPIOC)
        case PLUM_HAL_ID_GPIO_C_BASE:
            GPIOx = GPIOC;
            break;
#endif

#if defined(GPIOD)
        case PLUM_HAL_ID_GPIO_D_BASE:
            GPIOx = GPIOD;
            break;
#endif

#if defined(GPIOE)
        case PLUM_HAL_ID_GPIO_E_BASE:
            GPIOx = GPIOE;
            break;
#endif

#if defined(GPIOF)
        case PLUM_HAL_ID_GPIO_F_BASE:
            GPIOx = GPIOF;
            break;
#endif

#if defined(GPIOG)
        case PLUM_HAL_ID_GPIO_G_BASE:
            GPIOx = GPIOG;
            break;
#endif

#if defined(GPIOH)
        case PLUM_HAL_ID_GPIO_H_BASE:
            GPIOx = GPIOH;
            break;
#endif

#if defined(GPIOI)
        case PLUM_HAL_ID_GPIO_I_BASE:
            GPIOx = GPIOI;
            break;
#endif

#if defined(GPIOJ)
        case PLUM_HAL_ID_GPIO_J_BASE:
            GPIOx = GPIOJ;
            break;
#endif

#if defined(GPIOK)
        case PLUM_HAL_ID_GPIO_K_BASE:
            GPIOx = GPIOK;
            break;
#endif

        default:
            break;
    }

    return (GPIOx);
}

PLUM_PRIVATE plum_u16 hal_stm32_gpio_pin(plum_u32 id)
{
    plum_u16 pin = 0;

    switch (HAL_STM32_GPIO_GET_PIN(id)) {
#if defined(GPIO_PIN_0)
        case 0:
            pin = GPIO_PIN_0;
            break;
#endif

#if defined(GPIO_PIN_1)
        case 1:
            pin = GPIO_PIN_1;
            break;
#endif

#if defined(GPIO_PIN_2)
        case 2:
            pin = GPIO_PIN_2;
            break;
#endif

#if defined(GPIO_PIN_3)
        case 3:
            pin = GPIO_PIN_3;
            break;
#endif

#if defined(GPIO_PIN_4)
        case 4:
            pin = GPIO_PIN_4;
            break;
#endif

#if defined(GPIO_PIN_5)
        case 5:
            pin = GPIO_PIN_5;
            break;
#endif

#if defined(GPIO_PIN_6)
        case 6:
            pin = GPIO_PIN_6;
            break;
#endif

#if defined(GPIO_PIN_7)
        case 7:
            pin = GPIO_PIN_7;
            break;
#endif

#if defined(GPIO_PIN_8)
        case 8:
            pin = GPIO_PIN_8;
            break;
#endif

#if defined(GPIO_PIN_9)
        case 9:
            pin = GPIO_PIN_9;
            break;
#endif

#if defined(GPIO_PIN_10)
        case 10:
            pin = GPIO_PIN_10;
            break;
#endif

#if defined(GPIO_PIN_11)
        case 11:
            pin = GPIO_PIN_11;
            break;
#endif

#if defined(GPIO_PIN_12)
        case 12:
            pin = GPIO_PIN_12;
            break;
#endif

#if defined(GPIO_PIN_13)
        case 13:
            pin = GPIO_PIN_13;
            break;
#endif

#if defined(GPIO_PIN_14)
        case 14:
            pin = GPIO_PIN_14;
            break;
#endif

#if defined(GPIO_PIN_15)
        case 15:
            pin = GPIO_PIN_15;
            break;
#endif

        default:
            break;
    }

    return (pin);
}

PLUM_PRIVATE plum_void hal_stm32_gpio_clk_enable(plum_u32 id)
{
    switch (HAL_STM32_GPIO_GET_PORT(id)) {
#if defined(GPIOA)
        case PLUM_HAL_ID_GPIO_A_BASE:
            __HAL_RCC_GPIOA_CLK_ENABLE();
            break;
#endif

#if defined(GPIOB)
        case PLUM_HAL_ID_GPIO_B_BASE:
            __HAL_RCC_GPIOB_CLK_ENABLE();
            break;
#endif

#if defined(GPIOC)
        case PLUM_HAL_ID_GPIO_C_BASE:
            __HAL_RCC_GPIOC_CLK_ENABLE();
            break;
#endif

#if defined(GPIOD)
        case PLUM_HAL_ID_GPIO_D_BASE:
            __HAL_RCC_GPIOD_CLK_ENABLE();
            break;
#endif

#if defined(GPIOE)
        case PLUM_HAL_ID_GPIO_E_BASE:
            __HAL_RCC_GPIOE_CLK_ENABLE();
            break;
#endif

#if defined(GPIOF)
        case PLUM_HAL_ID_GPIO_F_BASE:
            __HAL_RCC_GPIOF_CLK_ENABLE();
            break;
#endif

#if defined(GPIOG)
        case PLUM_HAL_ID_GPIO_G_BASE:
            __HAL_RCC_GPIOG_CLK_ENABLE();
            break;
#endif

#if defined(GPIOH)
        case PLUM_HAL_ID_GPIO_H_BASE:
            __HAL_RCC_GPIOH_CLK_ENABLE();
            break;
#endif

#if defined(GPIOI)
        case PLUM_HAL_ID_GPIO_I_BASE:
            __HAL_RCC_GPIOI_CLK_ENABLE();
            break;
#endif

#if defined(GPIOJ)
        case PLUM_HAL_ID_GPIO_J_BASE:
            __HAL_RCC_GPIOJ_CLK_ENABLE();
            break;
#endif

#if defined(GPIOK)
        case PLUM_HAL_ID_GPIO_K_BASE:
            __HAL_RCC_GPIOK_CLK_ENABLE();
            break;
#endif

        default:
            break;
    }
}

PLUM_PRIVATE plum_s32 hal_stm32_gpio_irq_line(plum_u32 id)
{
    plum_s32 irq_num = HAL_STM32_GPIO_IRQLINE_INVAILD;

    switch (HAL_STM32_GPIO_GET_PIN(id)) {
        case 0:
            irq_num = EXTI0_IRQn;
            break;

        case 1:
            irq_num = EXTI1_IRQn;
            break;

        case 2:
            irq_num = EXTI2_IRQn;
            break;

        case 3:
            irq_num = EXTI3_IRQn;
            break;

        case 4:
            irq_num = EXTI4_IRQn;
            break;

        case 5:
        case 6:
        case 7:
        case 8:
        case 9:
            irq_num = EXTI9_5_IRQn;
            break;

        case 10:
        case 11:
        case 12:
        case 13:
        case 14:
        case 15:
            irq_num = EXTI15_10_IRQn;
            break;

        default:
            break;
    }

    return (irq_num);
}

PLUM_PRIVATE plum_s32
    plum_hal_gpio_irq_register(plum_u32 id, plum_void (*handler)(plum_void))
{
    struct gpio_irq *irq = plum_kal_mem_malloc(sizeof(struct gpio_irq));
    if (!irq) {
        return (PLUM_ECODE_EMEM);
    }

    memset(irq, 0, sizeof(struct gpio_irq));
    irq->pin     = hal_stm32_gpio_pin(id);
    irq->handler = handler;

    struct gpio_irq *ptr = &irq_hd;
    do {
        if (!ptr->next) {
            ptr->next = irq;
            break;
        }
        else if (ptr->next->pin == irq->pin) {
            ptr->next->handler = handler;
            plum_kal_mem_malloc(irq);
            break;
        }
        ptr = ptr->next;
    } while (ptr->next);

    return (PLUM_ECODE_OK);
}

/*****************************************************************************
 * Interrupt Functions
 ****************************************************************************/

plum_void HAL_GPIO_EXTI_Callback(plum_u16 GPIO_Pin)
{
    struct gpio_irq *ptr = irq_hd.next;

    while (ptr) {
        if (ptr->pin == GPIO_Pin) {
            ptr->handler();
            break;
        }
        ptr = ptr->next;
    }
}

void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

void EXTI1_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
}

void EXTI2_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
}

void EXTI3_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
}

void EXTI4_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
}

void EXTI9_5_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
}

void EXTI15_10_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
}

PLUM_PRIVATE plum_s32 plum_hal_gpio_dir(plum_u32                id,
                                        plum_hal_gpio_com_cof_t mode)
{
    hal_stm32_gpio_clk_enable(id);

    GPIO_TypeDef *port = hal_stm32_gpio_port(id);

    /* Configure GPIO_InitStructure */
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin              = hal_stm32_gpio_pin(id);
    GPIO_InitStruct.Mode             = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull             = GPIO_NOPULL;
    GPIO_InitStruct.Speed            = GPIO_SPEED_FREQ_HIGH;

    switch (mode) {
        case PLUM_HAL_GPIO_MODE_ANALOG:
            GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
            break;

        case PLUM_HAL_GPIO_MODE_INPUT:
            GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
            break;

        case PLUM_HAL_GPIO_MODE_INPUT_UP:
            GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
            GPIO_InitStruct.Pull = GPIO_PULLUP;
            break;

        case PLUM_HAL_GPIO_MODE_INPUT_DOWN:
            GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
            GPIO_InitStruct.Pull = GPIO_PULLDOWN;
            break;

        case PLUM_HAL_GPIO_MODE_OUT_PP:
            GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
            break;

        case PLUM_HAL_GPIO_MODE_OUT_OD:
            GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
            break;

        case PLUM_HAL_GPIO_MODE_OUT_OD_UP:
            GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
            GPIO_InitStruct.Pull = GPIO_PULLUP;
            break;

        case PLUM_HAL_GPIO_MODE_OUT_OD_DOWN:
            GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
            GPIO_InitStruct.Pull = GPIO_PULLDOWN;
            break;

        case PLUM_HAL_GPIO_MODE_AF_PP:
            GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
            break;

        case PLUM_HAL_GPIO_MODE_AF_OD:
            GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
            break;

        case PLUM_HAL_GPIO_MODE_AF_INPUT:
            GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;
            break;

        default:
            return (PLUM_ECODE_EPARA);
    }

    HAL_GPIO_Init(port, &GPIO_InitStruct);

    return (0);
}

PLUM_PRIVATE plum_s32 plum_hal_gpio_irq(plum_u32 id, plum_hal_gpio_irq_t irq,
                                        plum_void (*handler)(plum_void))
{
    plum_s32 rc = 0;

    do {
        if (!handler) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        plum_s32 irqnum = hal_stm32_gpio_irq_line(id);
        if (irqnum == HAL_STM32_GPIO_IRQLINE_INVAILD) {
            rc = PLUM_ECODE_EIO;
            break;
        }

        hal_stm32_gpio_clk_enable(id);

        rc = plum_hal_gpio_irq_register(id, handler);
        if (rc) {
            break;
        }

        GPIO_TypeDef *GPIOx = hal_stm32_gpio_port(id);

        /* Configure GPIO_InitStructure */
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        GPIO_InitStruct.Pin              = hal_stm32_gpio_pin(id);
        GPIO_InitStruct.Mode             = GPIO_MODE_IT_RISING;
        GPIO_InitStruct.Pull             = GPIO_NOPULL;
        GPIO_InitStruct.Speed            = GPIO_SPEED_FREQ_HIGH;
        switch (irq) {
            case PLUM_HAL_GPIO_IRQ_RISING_EDGE:
                GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
                GPIO_InitStruct.Pull = GPIO_PULLDOWN;
                break;

            case PLUM_HAL_GPIO_IRQ_FALLING_EDGE:
                GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
                GPIO_InitStruct.Pull = GPIO_PULLUP;
                break;

            case PLUM_HAL_GPIO_IRQ_BOTH_EDGE:
                GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
                break;

            case PLUM_HAL_GPIO_IRQ_HIGH_LEVEL:
            case PLUM_HAL_GPIO_IRQ_LOW_LEVEL:
                break;

            default:
                break;
        }
        HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);

        HAL_NVIC_SetPriority((IRQn_Type)irqnum, 5, 0);
        HAL_NVIC_EnableIRQ((IRQn_Type)irqnum);
    } while (0);

    return (rc);
}
/*****************************************************************************
 * Public Functions
 ****************************************************************************/

PLUM_PUBLIC
plum_s32 plum_hal_gpio_init(plum_u32 id, plum_hal_gpio_cof_t *cof)
{
    plum_s32 rc = PLUM_ECODE_EIO;

    if (cof->mode == PLUM_HAL_GPIO_MODE_COMMON) {
        rc = plum_hal_gpio_dir(id, cof->config.com);
    }
    else if (cof->mode == PLUM_HAL_GPIO_MODE_IRQ) {
        rc = plum_hal_gpio_irq(id, cof->config.irq.trip,
                               cof->config.irq.irq_handle);
    }

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_hal_gpio_write(plum_u32 id, plum_bit value)
{
    plum_s32      rc    = PLUM_ECODE_OK;
    GPIO_TypeDef *GPIOx = hal_stm32_gpio_port(id);

    if (GPIOx) {
        HAL_GPIO_WritePin(GPIOx, hal_stm32_gpio_pin(id), (GPIO_PinState)value);
    }
    else {
        rc = PLUM_ECODE_EIO;
    }

    return (rc);
}

PLUM_PUBLIC
plum_bit plum_hal_gpio_read(plum_u32 id)
{
    GPIO_TypeDef *GPIOx = hal_stm32_gpio_port(id);

    if (GPIOx) {
        return (plum_bit)HAL_GPIO_ReadPin(GPIOx, hal_stm32_gpio_pin(id));
    }
    else {
        return plum_false;
    }
}

PLUM_PUBLIC
plum_s32 plum_hal_gpio_irq_enable(plum_u32 id)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        plum_s32 irqline = hal_stm32_gpio_irq_line(id);
        if (irqline == HAL_STM32_GPIO_IRQLINE_INVAILD) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        HAL_NVIC_EnableIRQ((IRQn_Type)irqline);
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_hal_gpio_irq_disable(plum_u32 id)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        plum_s32 irqline = hal_stm32_gpio_irq_line(id);
        if (irqline == HAL_STM32_GPIO_IRQLINE_INVAILD) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        HAL_NVIC_DisableIRQ((IRQn_Type)irqline);
    } while (0);

    return (rc);
}

/****************************************************************************/
/*                                                                          */
/*  End of file.                                                            */
/*                                                                          */
/****************************************************************************/
